import numpy as np
def conj(x):
    """
    Returns the complex conjugate of x.
    
    Args:
        x: number, vector, matrix, array, or expression
        
    Returns:
        complex conjugate of x
    """
    if type(x) == int or type(x) == float:
        return x
    elif type(x) == complex:
        return complex(x.real, -x.imag)
    elif type(x) == list or type(x) == tuple or type(x) == np.ndarray:
        return [conj(val) for val in x]
    elif type(x) == np.matrix:
        return np.matrix([conj(val) for val in np.asarray(x)])
    else:
        raise TypeError("Input type not supported.")



def test_conj():
    # Test case 1
    x = 3 + 4j
    expected_output = 3 - 4j
    assert conj(x) == expected_output

    # Test case 2
    x = -2j
    expected_output = 2j
    assert conj(x) == expected_output

    # Test case 3
    x = 1
    expected_output = 1
    assert conj(x) == expected_output

    # Test case 4
    x = [1+2j, -3-4j, 5+6j]
    expected_output = [1-2j, -3+4j, 5-6j]
    print(conj(x))
    assert conj(x) == expected_output

    # Test case 5
    x = [[1+2j, 3+4j], [5+6j, 7+8j]]
    expected_output = [[1-2j, 3-4j], [5-6j, 7-8j]]
    assert conj(x) == expected_output

test_conj()
